﻿#include <stdio.h>
#include <stdbool.h>

#define _comp_ comp(int a, int b)

int comp1(int a, int b) { //升序
    return a - b;
}

int comp2(int a, int b) { //降序
    return b - a;
}

void printArray(int *nums, int size);

void swap(int *a, int *b);

//二分搜索
int binarySearch(int nums[], int left, int right, int target, int _comp_);

//冒泡排序
void bubbleSort(int *nums, int size, int _comp_);

//冒泡排序优化
void bubbleSortPlus(int *nums, int size, int _comp_);

//插入排序
void insertSort(int *nums, int size, int _comp_);

//插入排序的二分搜索算法优化
void insertSortPlus(int *nums, int size, int _comp_);

//选择排序
void selectSort(int *nums, int size, int _comp_);

//选择排序双轴优化
void selectSortPlus(int *nums, int size, int _comp_);

int main() {
    int test[10] = {9, 6, 5, 45, 3, 2, 1, 0, 7, 8};
    printArray(test, 10);
    bubbleSort(test, 10, comp2); //冒泡排序
    printArray(test, 10);
    bubbleSortPlus(test, 10, comp1); //冒泡排序优化
    printArray(test, 10);
    insertSort(test, 10, comp2); //插入排序
    printArray(test, 10);
    insertSortPlus(test, 10, comp1); //插入排序二分搜索算法优化
    printArray(test, 10);
    selectSort(test, 10, comp2); //选择排序
    printArray(test, 10);
    selectSortPlus(test, 10, comp1); //选择排序双轴优化
    printArray(test, 10);
    return 0;
}

void selectSortPlus(int *nums, int size, int _comp_) {
    int left = 0, right = size - 1;   //相当于左端和右端都是已经排好序的，中间是待排序的，所以说范围不断缩小
    while (left < right) {
        int min = left, max = right;
        for (int i = left; i <= right; i++) {
            if (nums[i] < nums[min]) min = i;   //同时找最小的和最大的
            if (nums[i] > nums[max]) max = i;
        }
        if (comp(1, 2) > 0) {
            swap(&nums[max], &nums[left]);
            if (min == left) min = max;
            swap(&nums[min], &nums[right]);
        } else {
            swap(&nums[max], &nums[right]);   //这里先把大的换到右边
            //注意大的换到右边之后，有可能被换出来的这个就是最小的，所以说需要判断一下
            //如果遍历完发现最小的就是当前右边排序的第一个元素
            //此时因为已经被换出来了，所以说需要将min改到换出来的那个位置
            if (min == right) min = max;
            swap(&nums[min], &nums[left]);   //接着把小的换到左边
        }
        left++;    //这一轮完事之后，缩小范围
        right--;
    }
}

void selectSort(int *nums, int size, int _comp_) {
    for (int i = 0; i < size - 1; ++i) { //因为最后一个元素一定是在对应位置上的，所以只需要进行N - 1轮排序
        int copy = i;
        for (int j = i + 1; j < size; ++j)   //挨个遍历剩余的元素，并更新
            if (comp(nums[copy], nums[j]) > 0) copy = j;
        int tmp = nums[i];    //找出元素之后，开始交换
        nums[i] = nums[copy];
        nums[copy] = tmp;
    }
}

void insertSortPlus(int *nums, int size, int _comp_) { //二分搜索算法来查找对应的插入位置
    for (int i = 1; i < size; ++i) {
        int tmp = nums[i];
        int j = binarySearch(nums, 0, i - 1, tmp, comp);   //由二分搜索来确定插入位置
        for (int k = i; k > j; k--) nums[k] = nums[k - 1];   //依然是将后面的元素后移
        nums[j] = tmp;
    }
}

void insertSort(int *nums, int size, int _comp_) {
    for (int i = 1; i < size; ++i) { //左牌堆默认有序
        int j = i, tmp = nums[i];
        while (j > 0 && comp(nums[j - 1], tmp) > 0) {
            nums[j] = nums[j - 1]; //不断进行后移操作，把位置腾出来
            --j;
        }
        nums[j] = tmp;
    }
}

void bubbleSortPlus(int *nums, int size, int _comp_) {
    for (int i = 0; i < size - 1; ++i) { //只需要size-1次即可
        bool flag = true; //这里使用一个标记，默认为`true`表示数组是有序的
        for (int j = 0; j < size - i - 1; ++j) {
            if (comp(nums[j], nums[j + 1]) > 0) {
                swap(&nums[j], &nums[j + 1]);
                flag = false; //如果发生交换，说明不是有序的，把标记变成`false`
            }
        }
        if (flag) break;//如果没有发生任何交换，flag一定是`true`，数组已经有序，所以说直接退出
    }
}

void bubbleSort(int *nums, int size, int _comp_) {
    for (int i = 0; i < size; ++i) {
        for (int j = 0; j < size - i - 1; ++j) {
            if (comp(nums[j], nums[j + 1]) > 0) swap(&nums[j], &nums[j + 1]);
        }
    }
}

int binarySearch(int nums[], int left, int right, int target, int _comp_) {
    int mid;
    while (left <= right) {
        mid = (left + right) >> 1;
        if (target == nums[mid]) return mid + 1;   //如果插入元素跟中间元素相等，直接返回后一位
        else if (comp(target, nums[mid]) < 0) {
            right = mid - 1;   //范围划到左边
        } else left = mid + 1;   //范围划到右边
    }
    return left;   //不断划分范围，left也就是待插入位置了
}

void swap(int *a, int *b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

void printArray(int *nums, int size) {
    for (int i = 0; i < size; ++i) {
        printf("%d ", nums[i]);
    }
    putchar('\n');
}
